import { lerror, llog, lwarn } from "../logger/Logger";
import { show_dialog, show_wait } from "../ui/SUI";
import { Socket, SocketConnectCallback, SocketErrorCallback } from "./Socket";
import { SOCKET_CLOSE_CODE, SocketProtoCoder } from "./ISocket";

export default class WebSock extends Socket {
    //  源 websocket
    protected _socket: WebSocket = null;
    // url
    private _url: string;
    private _binaryType: BinaryType = "arraybuffer";

    // 重连次数
    private _reconnectCount: number = 0;
    // 最大重连次数
    private _maxReconnectCount: number = 3;
    public get maxReconnectCount() {
        return this._maxReconnectCount;
    }

    public set maxReconnectCount(value: number) {
        this._maxReconnectCount = value;
    }

    // 等待关闭函数
    private _closeConnectWait: Function = null;

    // protoId -> closeWait
    private _closeHaltWaits: Map<number, Function> = new Map<number, Function>();

    public constructor(url: string, protoHandler: SocketProtoCoder, binaryType: BinaryType = "arraybuffer") {
        super(protoHandler);
        this._url = url;
        if (binaryType) {
            this._binaryType = binaryType;
        }
    }

    public get connected(): boolean {
        return this._socket && this._socket.readyState == WebSocket.OPEN;
    }

    public connect(onConnect?: SocketConnectCallback, onError?: SocketErrorCallback) {
        if (this._socket) {
            lwarn("socket is connecting or connected");
            return;
        }

        this.closeAllWait();
        this._closeConnectWait = show_wait("正在连接服务器");

        onConnect && this.connectCallback(onConnect);
        onError && this.errorCallback(onError);
        this._socket = new WebSocket(this._url);
        this._socket.binaryType = this._binaryType;
        this._socket.onopen = this.onConnect.bind(this);
        this._socket.onmessage = this.onReceive.bind(this);
        this._socket.onerror = this.onError.bind(this);
        this._socket.onclose = this.onClose.bind(this);
    }

    public onConnect(): void {
        this.closeAllWait();
        this.startHeartBeat();
        this._connectCallback && this._connectCallback(this);
    }

    public send(protoId: number, ...args: any[]): void {
        if (!this._socket || this._socket.readyState != WebSocket.OPEN) {
            lwarn("socket is not ready");
            return;
        }

        llog("socket send", `0x${protoId.toString(16).padStart(4, '0')}`, ...args);
        this._socket.send(this.protoHandler.encode(protoId, ...args));
    }

    public sendHalt(protoId: number, ...args: any[]): void {
        this.send(protoId, ...args);
        if (!this._closeHaltWaits.has(protoId)) {
            this._closeHaltWaits.set(protoId, show_wait());
        }
    }

    public request(protoId: number, ...args: any[]): void {
        if (typeof args[args.length - 1] == "function") {
            this.once(protoId, args[args.length - 1]);
            this.sendHalt(protoId, ...args.slice(0, -1));
        } else {
            this.sendHalt(protoId, ...args);
        }
    }

    public onReceive(event: MessageEvent<any>): void {
        let [protoId, ...data] = this.protoHandler.decode(event.data);
        llog("socket receive", `0x${protoId.toString(16).padStart(4, '0')}`, ...data);
        this.dispatch(protoId, ...data);
        let closeWait = this._closeHaltWaits.get(protoId);
        if (closeWait) {
            closeWait();
            this._closeHaltWaits.delete(protoId);
        }
    }

    public closeAllWait() {
        this._closeConnectWait && this._closeConnectWait();
        this._closeConnectWait = null;
        for (let closeWait of this._closeHaltWaits.values()) {
            closeWait();
        }

        this._closeHaltWaits.clear();
    }

    public onError(event: ErrorEvent): void {
        lerror("WebSocket error", event);
        this.dispose();
        this._errorCallback && this._errorCallback(event);
    }

    public reconnect() {
        if (this._reconnectCount >= this._maxReconnectCount) {
            this._reconnectCount = 0;
            show_dialog("连接服务器失败，请检查网络", this.reconnect.bind(this));
            return;
        }

        this._reconnectCount++;
        this.connect();
    }

    public onClose(event: CloseEvent) {
        llog("onclose", event.code, event.reason, event.wasClean);
        this.dispose();
        this._closeCallback && this._closeCallback(event);
    }

    public dispose() {
        this.stopHeartBeat();

        this._socket = null;
        this._reconnectCount = 0;
        this.closeAllWait();
    }

    public close(code: SOCKET_CLOSE_CODE = SOCKET_CLOSE_CODE.NORMAL, reason: string = "normal") {
        this.stopHeartBeat();
        if (!this._socket || this._socket.readyState !== WebSocket.OPEN) {
            llog("close invalid socket, socket status: ", this._socket?.readyState);
            return;
        }

        llog("close socket", code, reason);
        this._socket.close(code, reason);
    }
}